package libra;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.config.GatewayProperties;
import org.springframework.cloud.gateway.config.PropertiesRouteDefinitionLocator;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.route.CachingRouteLocator;
import org.springframework.cloud.gateway.route.CompositeRouteDefinitionLocator;
import org.springframework.cloud.gateway.route.InMemoryRouteDefinitionRepository;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator;
import org.springframework.context.annotation.DependsOn;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import reactor.core.publisher.Flux;

/**
 * 自定义Route路由修改接口Controller
 * 参照Gateway提供的Endpoint进行编写
 * 	org.springframework.cloud.gateway.actuate.GatewayControllerEndpoint
 * 基于NacosConfig实现Route规则持久化
 */
@RestController
@RequestMapping("route")
/** 
 * 因为依赖GatewayAutoConfiguration自动装配类的配置Bean
 * 所以通过@DependsOn滞后RouteController的创建，确保@Autowired的顺利注入
 * 参照
 * 	org.springframework.cloud.gateway.config.GatewayAutoConfiguration
 */
@DependsOn(value={"routeDefinitionLocator","routeDefinitionRouteLocator","cachedCompositeRouteLocator"})
public class NacosRouteController {
	
	/* 获取gateway在配置文件中的路由配置 */
	@Autowired
	GatewayProperties gatewayProp;
	
	/**
	 * 获取容器中存在的基于yml配置的RouteDefinitionLocator
	 */
	@Autowired
    PropertiesRouteDefinitionLocator PropertiesRouteDefinitionLocator;
	
	/**
	 * 获取容器中存在的RouteDefinitionRepository实例(InMemoryRouteDefinitionRepository)
	 * inMemoryRouteDefinitionRepository实现自RouteDefinitionRepository
	 * RouteDefinitionRepository继承自RouteDefinitionLocator、RouteDefinitionWriter
	 */
	@Autowired
    InMemoryRouteDefinitionRepository inMemoryRouteDefinitionRepository;

	/**
	 * 获取容器中存在的基于组合的RouteDefinitionLocator，beanID=routeDefinitionLocator
	 * 内部包含：
	 * 	PropertiesRouteDefinitionLocator、InMemoryRouteDefinitionRepository、DiscoveryClientRouteDefinitionLocator
	 */
	@Autowired
    CompositeRouteDefinitionLocator compositeRouteDefinitionLocator;
	
	/**
	 * 获取容器中存在的基于RouteDefinition的RouteLocator，beanID=routeDefinitionRouteLocator
	 * 内部包含容器中除了CachingRouteLocator、CompositeRouteLocator之外所有的RouteDefinition实现
	 */
	@Autowired
    RouteDefinitionRouteLocator routeDefinitionRouteLocator;

	/**
	 * 获取容器中存在的基于缓存的RouteLocator，beanID=cachedCompositeRouteLocator
	 * 内部包含：CompositeRouteLocator(内部包含：RouteDefinitionRouteLocator)
	 */
	@Autowired
	CachingRouteLocator cachingRouteLocator;
	
	@Autowired
	GatewayEventListenerHandler handler;
	
	
	/***********************************************************************************************************************
	 * 获取所有的Route，通过以下途径顺序链执行
	 * cachingRouteLocator -> 
	 * 		通过调用CompositeRouteLocator.getRoutes()获取Route
	 * compositeRouteLocator -> 
	 * 		通过调用所有容器中的RouteLocator实现的getRoutes()获取Route(CachingRouteLocator除外)
	 * 		内部包含RouteDefinitionRouteLocator & 其他自定义RouteLocator实现
	 * RouteDefinitionRouteLocator -> 
	 * 		通过调用CompositeRouteDefinitionLocator.getRouteDefinitions()获取RouteDefinition并转换为Route
	 * CompositeRouteDefinitionLocator -> 
	 * 		通过调用所有容器中的RouteDefinitionLocator实现的getRouteDefinitions()获取RouteDefinition
	 * 			PropertiesRouteDefinitionLocator(缓存yml配置中的route信息)
	 * 			InMemoryRouteDefinitionRepository(缓存内存中动态更新的route信息)
	 * 			DiscoveryClientRouteDefinitionLocator(缓存服务调用的route信息)
	 * 			!!!：DiscoveryClientRouteDefinitionLocator需要配置spring.cloud.gateway.enabled=true开启服务发现才会进行创建
	 **********************************************************************************************************************/
	@RequestMapping("all")
	public Object all() throws Exception {
		Map<String,List<Object>> map = new LinkedHashMap<String, List<Object>>();
		map.put("Route_cachingRouteLocator", toList(cachingRouteLocator.getRoutes().map(this::serialize)));
		map.put("Route_routeDefinitionRouteLocator", toList(routeDefinitionRouteLocator.getRoutes().map(this::serialize)));
		map.put("RouteDefinition_compositeRouteDefinitionLocator", toList(compositeRouteDefinitionLocator.getRouteDefinitions()));
		map.put("RouteDefinition_propertiesRouteDefinitionLocator", toList(PropertiesRouteDefinitionLocator.getRouteDefinitions()));
		map.put("RouteDefinition_inMemoryRouteDefinitionRepository", toList(inMemoryRouteDefinitionRepository.getRouteDefinitions()));
		return map;
	}
	// 供all()内部使用，用于将Flux转为List
	List<Object> toList( Flux<?> flux ){
		ArrayList<Object> arrayList = new ArrayList<Object>();
		flux.collectList()
			.doOnNext( arrayList::add )
			.subscribe( );// 必须调用subscribe(),否则流式编程不会执行
		return arrayList;
	}
	// 供all()内部使用，用于对Route进行简易封装，便于序列化
	Map<String, Object> serialize(Route route) {
		Map<String, Object> r = new LinkedHashMap<>();
		r.put("route_id", route.getId());
		r.put("uri", route.getUri().toString());
		r.put("order", route.getOrder());
		r.put("predicate", route.getPredicate().toString());
		if (!CollectionUtils.isEmpty(route.getMetadata())) {
			r.put("metadata", route.getMetadata());
		}
		ArrayList<String> filters = new ArrayList<>();
		for (int i = 0; i < route.getFilters().size(); i++) {
			GatewayFilter gatewayFilter = route.getFilters().get(i);
			filters.add(gatewayFilter.toString());
		}
		r.put("filters", filters);
		return r;
	}
	
	
	/**********************************************************************
	 * 新增Route路由
	 * 从InMemoryRouteDefinitionRepository获取当前缓存的Route列表
	 * 将RouteDefinition更新到缓存的Route列表，并将最新结果推送到NacosConfig
	 **********************************************************************/
	@RequestMapping("save")
	public Object save( @RequestBody RouteDefinition routeDefinition ) throws Exception{
		Map<String,RouteDefinition> localAll =  handler.getLocalAll(inMemoryRouteDefinitionRepository);
		localAll.put(routeDefinition.getId(), routeDefinition);
		
		handler.publishConfig(localAll.values());
		
        return Map.of("code",0);
	}
	
	
	/*******************************************************************
	 * 删除Route路由
	 * 从InMemoryRouteDefinitionRepository获取当前缓存的Route列表
	 * 将指定的routeID从缓存Route列表中删除，并将最新结果推送到NacosConfig
	 *******************************************************************/
	@RequestMapping("delete")
	public Object delete( String... routeIDs ) throws Exception{
		Map<String,RouteDefinition> localAll = handler.getLocalAll(inMemoryRouteDefinitionRepository);
		for( String routeID : routeIDs ) {
			localAll.remove(routeID);
		}
		handler.publishConfig(localAll.values());
		
        return Map.of("code",0);
	}
	
	
	/*******************************************************************************
	 * 清空Route路由
	 * 向NacosConfig推送空集合
	 * 即通过Listener清空当前InMemoryRouteDefinitionRepository中的缓存RouteDefinition
	 *******************************************************************************/
	@RequestMapping("clear")
	public Object clear() throws Exception {
		handler.publishConfig(null);
        return Map.of("code",0);
	}

}
